package com.example.java_message_queue.mqserver.core;

import com.example.java_message_queue.common.MqException;
import com.example.java_message_queue.mqserver.VirtualHost;
import com.example.java_message_queue.mqserver.datacenter.MemoryDataCenter;

// 实现消息路由功能
public class Router {
    public boolean route(ExchangeType exchangeType, Binding binding, Message message) throws MqException {
        // 根据不同的 exchangeType 进行不同的转发逻辑
        // DIRECT 的转发逻辑已经在外部判定过.
        if (exchangeType == ExchangeType.FANOUT) {
            return routeFanout(binding, message);
        } else if (exchangeType == ExchangeType.TOPIC) {
            return routeTopic(binding, message);
        } else {
            throw new MqException("[VirtualHost] 未知的 exchangeType! exchangeType=" + exchangeType);
        }
    }

    private boolean routeFanout(Binding binding, Message message) {
        // 对于 fanout 类型, 直接转发, 不需要进行任何匹配.
        return true;
    }

    // 需要按照通配符匹配
    // binding key 包含通配符
    // 1. * 表示任意一个 token 都可以匹配
    // 2. # 表示任意 0 个或 N 个 token 都可以匹配
    // 3. 其他内容则要求严格匹配.
    // 4. # 不会连续出现. # 和 * 不会相邻.
    // routing key 不包含通配符. 这个在发消息的时候校验
    // [测试用例]
    // binding key          routing key         result
    // aaa                  aaa                 true
    // aaa.bbb              aaa.bbb             true
    // aaa.bbb              aaa.bbb.ccc         false
    // aaa.bbb              aaa.ccc             false
    // aaa.bbb.ccc          aaa.bbb.ccc         true
    // aaa.*                aaa.bbb             true
    // aaa.*.bbb            aaa.bbb.ccc         false
    // *.aaa.bbb            aaa.bbb             false
    // #                    aaa.bbb.ccc         true
    // aaa.#                aaa.bbb             true
    // aaa.#                aaa.bbb.ccc         true
    // aaa.#.ccc            aaa.ccc             true
    // aaa.#.ccc            aaa.bbb.ccc         true
    // aaa.#.ccc            aaa.aaa.bbb.ccc     true
    // #.ccc                ccc                 true
    // #.ccc                aaa.bbb.ccc         true
    private boolean routeTopic(Binding binding, Message message) {
        // 按照 . 来切分 binding key 和 routing key
        String[] bindingTokens = binding.getBindingKey().split("\\.");
        String[] routingTokens = message.getRoutingKey().split("\\.");
        // 使用双指针的方式来实现匹配
        // 1. 如果是普通字符, 直接匹配内容是否相等, 不相等则返回 false, 相等直接进入下一轮
        // 2. 如果是 * , 直接进入下一轮
        // 3. 如果 # 没有下一个位置, 则直接返回 true
        // 4. 如果遇到 # , 则找到 # 下一个位置的 token 在 routingKey 中的位置.
        // 5. 如果能找到对应的位置了, 就可以继续匹配. 如果找不到, 就返回 false
        // 6. 循环结束后, 检查看两个下标是否同时到达末尾. 是则匹配成功, 否则匹配失败.
        int bindingIndex = 0;
        int routingIndex = 0;
        while (bindingIndex < bindingTokens.length && routingIndex < routingTokens.length) {
            if (bindingTokens[bindingIndex].equals("*")) {
                // 2. 如果是 * , 直接进入下一轮
                // 直接进入下一轮比较
                bindingIndex++;
                routingIndex++;
            } else if (bindingTokens[bindingIndex].equals("#")) {
                bindingIndex++;
                if (bindingIndex == bindingTokens.length) {
                    // 3. 如果 # 没有下一个位置, 则直接返回 true
                    return true;
                }
                // 4. 如果遇到 # , 则找到 # 下一个位置的 token 在 routingKey 中的位置.
                routingIndex = findNextMatch(routingTokens, routingIndex, bindingTokens[bindingIndex]);
                // 5. 如果能找到对应的位置了, 就可以继续下一轮匹配. 如果找不到, 就返回 false
                if (routingIndex == -1) {
                    return false;
                }
                bindingIndex++;
                routingIndex++;
            } else {
                // 1. 如果是普通字符, 直接匹配内容是否相等, 不相等则返回 false, 相等直接进入下一轮
                if (!bindingTokens[bindingIndex].equals(routingTokens[routingIndex])) {
                    return false;
                }
                bindingIndex++;
                routingIndex++;
            }
        }

        // 如果两方不能同时结束, 则也视为匹配失败.
        // 比如 aaa.*.bbb 和 aaa.bbb
        if (bindingIndex == bindingTokens.length && routingIndex == routingTokens.length) {
            return true;
        }
        return false;
    }

    private int findNextMatch(String[] routingTokens, int routingIndex, String bindingToken) {
        for (int i = routingIndex; i < routingTokens.length; i++) {
            if (routingTokens[i].equals(bindingToken)) {
                return i;
            }
        }
        return -1;
    }

    // 需要考虑通配符, 复杂一些
    public boolean checkBindingKeyValid(String bindingKey) {
        // 1. 允许是空字符串
        // 2. 数字字母下划线构成
        // 3. 可以包含通配符
        // 4. # 不能连续出现.
        // 5. # 和 * 不能相邻
        if (bindingKey.length() == 0) {
            return true;
        }
        // 先判定基础构成
        for (int i = 0; i < bindingKey.length(); i++) {
            char ch = bindingKey.charAt(i);
            if (ch >= 'A' && ch <= 'Z') {
                continue;
            }
            if (ch >= 'a' && ch <= 'z') {
                continue;
            }
            if (ch >= '0' && ch <= '9') {
                continue;
            }
            if (ch == '.' || ch == '_' || ch == '*' || ch == '#') {
                continue;
            }
            return false;
        }
        // 再判定每个词的情况
        // 比如 aaa.a*a 这种应该视为非法.
        String[] words = bindingKey.split("\\.");
        for (String word : words) {
            if (word.length() > 1 && (word.contains("*") || word.contains("#"))) {
                return false;
            }
        }
        // 再判定相邻词的情况
        for (int i = 0; i < words.length - 1; i++) {
            // 连续两个 ##
            if (words[i].equals("#") && words[i + 1].equals("#")) {
                return false;
            }
            // # 连着 *
            if (words[i].equals("#") && words[i + 1].equals("*")) {
                return false;
            }
            // * 连着 #
            if (words[i].equals("*") && words[i + 1].equals("#")) {
                return false;
            }
        }
        return true;
    }

    // 不包含通配符, 规则更简单.
    public boolean checkRoutingKeyValid(String routingKey) {
        if (routingKey.length() == 0) {
            return true;
        }
        // 数字字母下划线构成
        for (int i = 0; i < routingKey.length(); i++) {
            char ch = routingKey.charAt(i);
            if (ch >= 'A' && ch <= 'Z') {
                continue;
            }
            if (ch >= 'a' && ch <= 'z') {
                continue;
            }
            if (ch >= '0' && ch <= '9') {
                continue;
            }
            if (ch == '_' || ch == '.') {
                continue;
            }
            return false;
        }
        return true;
    }
}
